#region license
# This file is part of Grawlix.
# 
# Grawlix is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# 
# Grawlix is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public
# License along with Grawlix.  If not, see
# <http://www.gnu.org/licenses/>.
#endregion

"""Automatic log4net logging compile-time macro syntax."""

namespace Grawlix.Meta
import Boo.Lang.Compiler
import System

transient class ExpectMacro(LexicalInfoPreservingMacro):
"""
Macro used for expanding test cases.
Remarks: Using this macro will make the compilation unit dependent on NUnit.
"""
	
	override def ExpandImpl(macro as Ast.MacroStatement):
	"""
	Apply the macro.
	Param macro: The macro being expanded.
	Returns: The block to replace it with.
	"""
		_macro = macro
		#markNamespaceUsed(enclosingModule(_macro), "Grawlix.Meta.Testing")
		if _macro.Arguments.Count < 1:
			Usage("The expect macro requires at least 1 argument.")
		_checks = Ast.Block(_macro.LexicalInfo)
		result as Ast.Statement
		if not _macro.Block.HasStatements:
			result = CreateNormalVersion()
			if _macro.Arguments.Count == 1:
				IsTrueCheck()
		else:
			result = CreateExceptionVersion()
		skip = true
		for arg in _macro.Arguments:
			if skip:
				skip = false
				continue
			_checks.Add(GenerateAssertion(arg))
		return result
	
	private def CreateNormalVersion():
	"""Intialize a normal expectation."""
		_lhs = Ast.AstUtil.CreateReferenceExpression(
			_macro.LexicalInfo,
			"___expect${Context.AllocIndex()}")
		lhsExpression = _macro.Arguments[0]
		_lhsText = lhsExpression.ToCodeString()
		assign = Ast.BinaryExpression(
			_macro.LexicalInfo,
			Ast.BinaryOperatorType.Assign,
			_lhs,
			lhsExpression)
		return [|
			$assign
			$_checks
		|]
	
	private static def FindExceptionType(expression as Ast.Expression):
	"""
	Figures out the exception type specified.
	Param expression: The expression given.
	Returns: The exception type specified.
	"""
		generic = expression as Ast.GenericReferenceExpression
		if generic is not null:
			simple = generic.Target as Ast.ReferenceExpression
			if simple is not null:
				return Ast.GenericTypeReference(
					expression.LexicalInfo,
					GenericArguments: generic.GenericArguments)
		simple = expression as Ast.ReferenceExpression
		if simple is not null:
			return Ast.SimpleTypeReference(expression.LexicalInfo, simple.Name)
		Usage(
			"When used in the exception catching form, " +
			"the first argument to the expect macro must " +
			"be the type of exception to expect.")
	
	private static def NoExceptionMessage(exceptionType as Ast.TypeReference):
	"""
	Gets the message for no exception being raised.
	Param exceptionType: The type of exception to report as missing.
	Returns: A code statement generating the error.
	"""
		failText = \
			"test failed:\n" + \
			"Expected exception of type ${exceptionType}, but " + \
			"no exception was raised."
		return [| NUnit.Framework.Assert.Fail($failText) |]
	
	private static def WrongExceptionMessage(exceptionType as Ast.TypeReference):
	"""
	Gets the message for the wrong exception being raised.
	Param exceptionType: The type of exception to report as wrong.
	Returns: A code statement generating the error.
	"""
		failText = \
			"test failed:\n" + \
			"Expected exception of type ${exceptionType}, but " + \
			"instead received "
		return [| NUnit.Framework.Assert.Fail($failText + exception) |]
	
	private def CreateExceptionVersion():
	"""
	Intializes an exceptional expectation.
	Returns: The expanded code.
	"""
		_lhs = \
			Ast.AstUtil.CreateReferenceExpression(
				_macro.LexicalInfo,
				"exception")
		exceptionType = FindExceptionType(_macro.Arguments[0])
		_lhsText = "exception"
		noFail = NoExceptionMessage(exceptionType)
		wrongFail = WrongExceptionMessage(exceptionType)
		block = _macro.Block
		return [|
			try:
				$block
				$noFail
			except exception as $exceptionType:
				if exception isa NUnit.Framework.AssertionException:
					raise
				$_checks
			except exception:
				if exception isa NUnit.Framework.AssertionException:
					raise
				$wrongFail
		|]
	
	private def IsTrueCheck():
	"""Generate the IsTrueCheck."""
		trueCheck = [|
			NUnit.Framework.Assert.IsTrue(
				$_lhs,
				"test failed:\n" + $_lhsText)
		|]
		_checks.Add(trueCheck)
	
	private def ConvertToExceptionCheck(expression as Ast.Expression):
	"""
	Tries converting to the exception message check.
	Param expression: The expression to assert.
	Returns: A modified exp or the same.
	"""
		if not _macro.Block.HasStatements:
			return expression
		stringLiteral = expression as Ast.StringLiteralExpression
		if stringLiteral is null:
			return expression
		return [| _.Message == $stringLiteral |]
		
	
	private def GenerateAssertion(expression as Ast.Expression):
	"""
	Generates an assertion statement.
	Param expression: The expression to assert.
	Returns: An assertion statement.
	"""
		expression = ConvertToExceptionCheck(expression)
		replacement = expression.Clone() as Ast.Expression
		replaceIdentifier(replacement, "_", _lhs.Name)
		replaceIdentifier(expression, "_", _lhsText)
		replacementText = \
			"test failed:\n${expression}\n" + \
			"with ${_lhsText} == "
		check = [|
			NUnit.Framework.Assert.IsTrue(
				$replacement,
				$replacementText + cast(object, $_lhs))
		|]
		check.LexicalInfo = expression.LexicalInfo
		return check
		
	private static def Usage(reason as string):
	"""
	Raise a usage statement.
	Param reason: The cause of the error.
	"""
		raise ArgumentException(
			reason)
#			"Make sure that you invoke the macro with the proper syntax.")
	
	private _macro as Ast.MacroStatement
	"""The macro to apply against."""
	
	private _lhs as Ast.ReferenceExpression
	"""What to test assertions against."""
	
	private _lhsText as string
	"""A string representation of the lhs expression."""
	
	private _checks as Ast.Block
	"""Where to add the assertions."""
	
	